package net.Group;

import net.jxta.credential.AuthenticationCredential;
import net.jxta.credential.Credential;
import net.jxta.discovery.DiscoveryService;
import net.jxta.document.Advertisement;
import net.jxta.document.AdvertisementFactory;
import net.jxta.document.StructuredDocument;
import net.jxta.document.StructuredTextDocument;
import net.jxta.exception.PeerGroupException;
import net.jxta.exception.ProtocolNotSupportedException;
import net.jxta.id.IDFactory;
import net.jxta.impl.id.UUID.UUIDFactory;
import net.jxta.impl.protocol.PeerGroupAdv;
import net.jxta.peergroup.PeerGroupID;
import net.jxta.peergroup.PeerGroupFactory;
import net.jxta.impl.peergroup.StdPeerGroupParamAdv;
import net.jxta.membership.Authenticator;
import net.jxta.membership.MembershipService;
import net.jxta.peergroup.PeerGroup;
import net.jxta.protocol.ModuleImplAdvertisement;
import net.jxta.protocol.PeerGroupAdvertisement;
import net.Group.MembershipService.*;
import java.util.Enumeration;
import java.util.Hashtable;

public class GroupManager {
	private static final org.apache.log4j.Category LOG = 
        org.apache.log4j.Category.getInstance(GroupManager.class.getName());
    protected DiscoveryService disco;
    // The parent group of the current peer group
    protected PeerGroup parent;
    // Net peer for creating advertisements.
    protected PeerGroup netPeerGroup;
    // The current peer group
    protected PeerGroup activeGroup;
    // Any module(like a peer group) that is loaded by the parent group,must 
    // be compatible with the parent.Compatibility is checked by comparision 
    // with the stdCompactStatement 
    public StructuredTextDocument stdCompatStatement = // ??? Taken from StdPeerGroup
                mkCS();
    // An identifier to the implementation used // ??? Is it so ?
    public String stdUri = "http://www.jxta.org/download/jxta.jar";// ??? Taken from StdPeerGroup
    // The provider of the implementation // ??? Is it so ?
    public String stdProvider = "sun.com";// ??? Taken from StdPeerGroup

    // The mime type of all documents used in this example
    public static final String DOCUMENT_MIME_TYPE="text";
    // The base type of all documents
    public static final String DOCUMENT_BASE_TYPE="xml";
    // This is the root element documents created
    public static final String DOCUMENT_ROOT_ELEMENT="Comp";    
    // Key ??? What is this used for ?
    private static final String KEY="Efmt";    
    // Value ??? What is this used for ?
    private static final String VALUE="JDK1.4";
    // Key used to represent the binding
    private static final String BINDING_KEY="Bind";
    // Value of the binding key.It represents the binding 
    // of JXTA that we use.
    private static final String BINDING_VALUE="V1.0 Ref Impl";
   
    /** 
     * The constructor of the Group Manger.Initially 
     * the parent group is the also the active group.
     */
     
    public GroupManager(PeerGroup netPeerGroup, PeerGroup parent) {
   
        this.disco  = parent.getDiscoveryService();
        this.parent = parent;
        this.activeGroup = parent;
        this.netPeerGroup = netPeerGroup;
        if (netPeerGroup ==  null){
            System.out.println("netPeerGroup :"+netPeerGroup+" - aborting");
            throw new NullPointerException("netPeerGroup :"+netPeerGroup+" - aborting");
            
        }
   }
    
    /* 
     *   Method to add a new peer group and publish it.The 
     *   groupMemebershipClassName is the fully qualified class 
     *   name of the mermbership service class.
     *
     */
     
//    public PeerGroup addGroup(String groupName,String groupMembershipClassName,
//                              String groupDescription,
//                              ModuleImplAdvertisement advertisement,
//                              boolean conditional){
//System.out.println("GroupManager.addGroup Successfully created SecurityDemoGroup");
//        // The first thing we do is to see if a group already exists by this name.
//        // If so we get the Group ID of the group. Then depending on the unconditional
//        // flag we proceed.
//        // If it is an conditional add ,we create a new group only if no other group
//        // with the same name already exists otherwise we throw an exception.
//        // If it is an unconditional add , we always create  a group.If the user had previously
//        // created the group , we use the Old group ID . 
//        PeerGroupID oldPeerGroupID = alreadyExists(groupName);
//        if(oldPeerGroupID != null && conditional==true)
//            throw new GroupManagerException("A Group by this name already exists with id :"
//                                             +oldPeerGroupID);
//        
//        // If no Advertisement is provided, we create a fresh advertisement.
//        if (advertisement == null){
//            LOG.debug("Creating a new Advertisement");
//            // We use the existing advertisement of the standard peer group
//            // and add our service along with the other standard services 
//            try{
//                // This is used as a base to create the new advertisement upon    
//                System.out.println("netPeerGroup :"+netPeerGroup);
//                advertisement = netPeerGroup.getAllPurposePeerGroupImplAdvertisement();
//                //advertisement = parent.getAllPurposePeerGroupImplAdvertisement();//<< Did not work in current platform
//                StructuredDocument paramDoc = advertisement.getParam();
//                System.out.println("StructuredDocument paramDoc:"+paramDoc);
//                // The Param document used to make the StandradPeerGroup Advertisement
//                StdPeerGroupParamAdv paramAdv = new StdPeerGroupParamAdv(paramDoc);
//                // List of all the available standard services
//                Hashtable services = paramAdv.getServices();
//                // Make a ModuleImplAdvertisemnet for the membership service
//                ModuleImplAdvertisement moduleAdv = 
//                    mkImplAdvBuiltin(PeerGroup.refMembershipSpecID,groupMembershipClassName,
//                                     groupDescription);
//                // Add this service along the other standard services            
//                services.put(PeerGroup.membershipClassID, moduleAdv);
//                paramAdv.setServices(services);
//                advertisement.setParam((net.jxta.document.TextElement)paramAdv.getDocument(
//                              new net.jxta.document.MimeMediaType(DOCUMENT_MIME_TYPE,
//                                                                  DOCUMENT_BASE_TYPE)));
//            }catch(PeerGroupException peerGroupException){
//                peerGroupException.printStackTrace();
//                System.exit(-1);
//                LOG.error("Error in creating Advertisement",peerGroupException);
//                throw new GroupManagerException(peerGroupException.getMessage());
//            }catch(Exception genericException){
//                genericException.printStackTrace();
//                LOG.error("Error in creating Advertisement",genericException);
//                System.exit(-1);
//                throw new GroupManagerException(genericException.getMessage());
//            }
//        }
//        LOG.debug("Successfullt created ADVERTISEMENT");  
//        
//        // initialize but to no start the application
//        // this is done by the join command
//        LOG.debug("Creating the Peer Group");        
//        PeerGroup peerGroup = null;
//        try {
//            // create a PeerGroup ID
//            PeerGroupID peerGroupID = null;
//            if(oldPeerGroupID != null){
//                peerGroupID = oldPeerGroupID;
//            }else{
//            	// what the problem in here??
//                //peerGroupID = new net.jxta.impl.id.UUID.PeerGroupID(UUIDFactory.newUUID());
//            	peerGroupID = IDFactory.newPeerGroupID( netPeerGroup.getPeerGroupID(), null );
//
//            }
//            // create the PeerGroup
//            peerGroup = parent.newGroup(peerGroupID, advertisement, groupName, groupDescription);////
//            // initialize the peergroup
//            
//            //kind of loop here!
//            //peerGroup.init(this.parent,peerGroupID, advertisement);
//        } catch (PeerGroupException peerGroupException) {
//            peerGroupException.printStackTrace();
//            LOG.error("Unable to create a peer group !",peerGroupException);
//            throw new GroupManagerException(peerGroupException.getMessage());
//        }
//        // For debug purposes, print advertisement to console
//        //com.sams.jxta.Util.printAdvertismentDoc(advertisement);
//        // Try to publish the advertisement
//        LOG.debug("Trying to publish the advertisement");
//        publish(peerGroup, advertisement/*original advertisement???*/);
//        LOG.debug("Peer Group Advertisement successfully published");
//        return peerGroup;
//  
//   }

    /** 
     * Will check if a peer group with the same name exists on this peer
     */

//    public PeerGroupID alreadyExists(String name){
//    
//        DiscoveryService discovery = parent.getDiscoveryService();
//        Enumeration enumeration =null;
//        try{
//            enumeration =
//                discovery.getLocalAdvertisements(discovery.GROUP, "Name",name);
//        } catch(java.io.IOException ioException) {
//            LOG.debug("Error in getting local advertisements ");
//            return null;
//        }
//        // If the group already exists either the  enumeration is null
//        // or it does not contain any data 
//        
//        if(enumeration != null && enumeration.hasMoreElements())
//            return ((PeerGroupAdv)enumeration.nextElement()).getPeerGroupID();
//        else
//            return null; 
//    }

    // Tries to publish the newly created peer group  
    // ??? Why do we need the original Advertisement ?
//    private void publish(PeerGroup child,Advertisement pgAdv
//                               //PeerGroupAdvertisement origAdv
//                               ) {
//      System.out.println("Publishing group");
//      //get the Discovery for this group
//      //Publish the New Peer in its group discovery
//      
//      DiscoveryService discovery;
//      try {
//         discovery = parent.getDiscoveryService();
//         discovery.publish(pgAdv, DiscoveryService.GROUP);
//         // let's publish the peer adv
//         //if (origAdv != null){//?????? Not sure what this does
//            // we could check if it is indeed ours
//            // but it does not hurt to publish it any way
//         //   discovery = child.getDiscoveryService();
//         //   discovery.publish(origAdv, DiscoveryService.GROUP);
//         //}
//      } catch (java.io.IOException ioException) {
//         LOG.error("Could not publish the service !",ioException);
//         throw new GroupManagerException(ioException.getMessage());
//      }
//      LOG.debug("Published the group successfully");
//    }

    /**
     *  This is the code that will create a credential to join the group.
     *  Each group that we are joining has a specific membership requirement.
     *  Many groups will just be the NullMembership, which is the default.
     *
     */
     
    public Credential joinGroup(PeerGroup newGroup,StructuredDocument credentials,
                               String authenticationMethod) 
                               throws GroupManagerAuthenticationException,
                               ProtocolNotSupportedException{

        MembershipService membership = (MembershipService) newGroup.getMembershipService();
        // The first step is to apply to the membership service
        Authenticator authenticator;
        authenticator = applyToMembershipService(newGroup,credentials,authenticationMethod);
        // Next step is to allow the user to "fill up" the authenticator
        // by creating a dialog
        LOG.debug("Apply process successful");
        System.out.println("Apply process successful");
        authenticate(authenticator);
        // third , try to join the service
        Credential authenticatedCredential =joinMembershipService(authenticator,newGroup);
        
        // All is fine ! We can switch to the new group
        activeGroup = newGroup;           
        LOG.debug("JOIN SUCCESSFUL !");
        
        System.out.println("JOIN SUCCESSFUL !");
        
        return authenticatedCredential;
        
    }

    /**
     * This code demonstrates how to apply to a membership service.
     */
     
    private Authenticator applyToMembershipService(PeerGroup peerGroup,
                                                   StructuredDocument credentials,
                                                   String authenticationMethod)
                                                   throws GroupManagerAuthenticationException,
                                                   ProtocolNotSupportedException{
        Authenticator authenticator = null;
        // Here we create an authentication credential and 
        // try to apply for a Authenticator.An exception is thrown in case
        // this step fails.
        try {
            
            AuthenticationCredential authenticationCredential =
                    new AuthenticationCredential(peerGroup,authenticationMethod,credentials );
            MembershipService membership = (MembershipService) peerGroup.getMembershipService();
            authenticator = (Authenticator)membership.apply( authenticationCredential );
            
        } catch( PeerGroupException peerGroupException ) {
            // This denotes a failure in the Apply process.We
            // consider this as an Authentication exception.
            LOG.error("Apply process failed !! ",peerGroupException);
                throw new GroupManagerAuthenticationException(peerGroupException.getMessage());
        } catch (ProtocolNotSupportedException protocolNotSupportedException){
                LOG.error(protocolNotSupportedException);
                throw protocolNotSupportedException;  
        }
        return authenticator;                                        
    }

        
    /** 
     * This method will help the user to actually "fill up" the authenticator
     * details. It will display a dialog and enable the user to visually 
     * provide details to each parameter of the Authenticator.The parameters
     * of the Authenticator are found by Introspection.
     */
     
    private void authenticate( Authenticator authenticator ){
        // The following bean looks for standard bean parameters to create the
        // contents to set the authenticator. There is no real spec so this
        // will have to do.
        //AuthenticatorBean viewBean = new AuthenticatorBean(null,
        //                                 true,authenticator,
        //                                 "Please Enter Required Group Info");
        //viewBean.setVisible(true);
    	
    	// do your own authentication process HERE!!!
    	// TO DO
    	
    }
    
    /**
     * This code demonstrates how to join a membership service.
     */
     
    private Credential joinMembershipService(Authenticator authenticator,
                                             PeerGroup peerGroup)
                                             throws GroupManagerAuthenticationException {
                                             
        Credential authenticatedCredential = null;                                    
        // We check if the user is authentic
        if( !authenticator.isReadyForJoin() ) {
            LOG.error( "Authenticator is not ready to join");
            throw new GroupManagerAuthenticationException("Authenticator is not ready to join !");
        }
         
        // Since the user is authentic , we allow the 
        // user to join the service.But the service may reject the
        // user as well.
         
        try{
            MembershipService membership = (MembershipService) peerGroup.getMembershipService();
            authenticatedCredential= membership.join(authenticator);
        } catch(PeerGroupException peerGroupException) {
             LOG.error( "Error in the Join Process",peerGroupException);
             throw new GroupManagerAuthenticationException("Error in the Join Process");
        }
        return authenticatedCredential;                                                     
    }
  
    /**
     * Method used to quit the current active group.
     */
    
    public void leaveGroup(){
    
        MembershipService memberShipService = activeGroup.getMembershipService();
        try{
            memberShipService.resign();    
        } catch(PeerGroupException peerGroupException){
            LOG.error("Exception in resign",peerGroupException);
            throw new GroupManagerException(peerGroupException.getMessage());
        }
        activeGroup = parent;
        purgeGroups();
    }
   
    /*
     * The method to renew the membership to the currently active group. 
     */
     
    public void renew(Credential credential){
        // ??? Seems that the implementation does not have a concept of renewal
    }
   
    /**
     * Purge all groups from the cache. This help prevent us from creating
     * a copy of a group with the same ID.
     *
     */
     
    public void purgeGroups(){
        DiscoveryService discovery = parent.getDiscoveryService();
        try{
            discovery.flushAdvertisements(null,DiscoveryService.GROUP);
        } catch (java.io.IOException ioException) {
            LOG.error("Error in purging Groups",ioException);
            throw new GroupManagerException(ioException.getMessage());
        }
   }

    /**
     * Creates an ModuleImplAdvertisement which in this context is a 
     * MembershipService.
     * 
     */
     
    private ModuleImplAdvertisement mkImplAdvBuiltin(
                                    net.jxta.platform.ModuleSpecID specID,
                                    String code,String descr) {
                                    
        String moduleImplAdvType = ModuleImplAdvertisement.getAdvertisementType();
        ModuleImplAdvertisement implAdvertisement = (ModuleImplAdvertisement)
                    AdvertisementFactory.newAdvertisement(moduleImplAdvType);
        implAdvertisement.setModuleSpecID(specID);
        implAdvertisement.setCompat(stdCompatStatement);
        implAdvertisement.setCode(code);
        implAdvertisement.setUri(stdUri);
        implAdvertisement.setProvider(stdProvider);
        implAdvertisement.setDescription(descr);
        return implAdvertisement;
   }


    /**
     * Creates a compatibility segment for a ModuleImplAdvertisement.
     * This is needed so that our parent group can load modules of the 
     * reference implementation also.
     */
     
     //??? Taken from StdPeerGroup because it was private. This should have 
     //* been in a utility class.
    protected StructuredTextDocument mkCS() {
                                          
         StructuredTextDocument doc = (StructuredTextDocument)
            net.jxta.document.StructuredDocumentFactory.newStructuredDocument(
                new net.jxta.document.MimeMediaType(DOCUMENT_MIME_TYPE,
                                                    DOCUMENT_BASE_TYPE),
                                                    DOCUMENT_ROOT_ELEMENT);
         net.jxta.document.Element e = doc.createElement(KEY, VALUE);
         doc.appendChild(e);
         e = doc.createElement(BINDING_KEY, BINDING_VALUE);
         doc.appendChild(e);
         return doc;
    }
    
    /**
     * This method is used for refreshing the peer with remotely published advertisements.
     * //???Makes little sense without a listener.Moreover the method is deprecated
     */

    public void refreshGroups(){
    
        //disco.getRemoteAdvertisements(null, DiscoveryService.GROUP, null, null,100);
    }

    /*
     * Method used to get the currently active group.
     */

    public PeerGroup getActiveGroup(){
    
        return activeGroup;           
    }
}
